/*
 *  ether-pxausb.c : "eth-over-usb" driver for BLOB
 *
 *  Copyright (c) 2003, Intel Corporation (yu.tang@intel.com)
 *
 * (C) Copyright 2006 Marvell International Ltd.
 * All Rights Reserved
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 */

#include "types.h"
#include "arch.h"
#include "time.h"
#include "mhn-regs.h"
#include "xllp_u2d.h"
#include "mhn_u2d_ctl.h"
#include "util.h"
#include "asm/errno.h"


#define MHNU2D_DBG 0
#if MHNU2D_DBG
#define DBGOUT(x) x
#else
#define DBGOUT(x)     /* gcc will remove all the debug code for us */
#endif

/* cable connection : defined in bvd_usb_ctl.c */
extern int usb_connected;
extern int usb_speed;
extern int tftp_t;
extern int arp_t;
void print_buf(char *buf, int num);

void
u2d_int_hndlr(int irq, void *dev_id);

/* receive buffer management */
/*static*/ u8  rx_buf[1024];
static int  rx_size = 0;
static int  rx_done = 0 ;

/* transmit buffer management */
static int tx_done = 0 ;

#define DELAY() do { \
        __asm__ volatile (\
        "mov ip, #1000\n"\
        "1: mov r0, r0\n"\
        "sub ip, ip, #1\n"\
        "cmp ip, #0\n"\
        "bne    1b\n");\
    }while(0);

u16 usb_vendor_id = 0x0525;
u16 usb_product_id = 0xa4a7;

extern void u2d_start(void);

static void usb_eth_tx_callback(int flag, int size)
{
    TRACEOUT
    tx_done = 1;
}

#if 0
static void usb_eth_rx_callback(int flag, int size)
{
    if( flag != 0 ) {
        return;
    }

    if (rx_done) {
        /* drop */
        rx_done = 0;
        rx_size = 0;
    }

    rx_size += size;

    rx_done = 1;

    /* setup to receive */
    ep2_recv(rx_buf, sizeof(rx_buf));

    return;
}
#endif

void usb_driver_speed(int speed)
{
    desc_t * pdesc = pxa_usb_get_descriptor_ptr();
    config_desc_t *cfg;
    intf_desc_t *intf;
    acm_function * acm;
    intf_desc_t *intf_b;
    ep_desc_t *ep;

    cfg = (config_desc_t*) (pdesc->cdb);
    intf = (intf_desc_t *) (cfg + 1);//?
    acm = (acm_function *) (intf+1);
    intf_b = (intf_desc_t *) ( acm + 1);
    ep = (ep_desc_t *) (intf_b + 1);

    if( speed == USB_SPEED_HIGH ) {
        ep[0].wMaxPacketSize      = make_word( 512 );
        ep[1].wMaxPacketSize      = make_word( 512 );
    }
    else {
        ep[0].wMaxPacketSize      = make_word( 64 );
        ep[1].wMaxPacketSize      = make_word( 64 );
    }
}

void usb_driver_reset(void)
{
    DBGOUT(printf("In usb_driver_reset, usb_speed= %d\n", usb_speed));
    desc_t * pdesc = pxa_usb_get_descriptor_ptr();
    config_desc_t *cfg;
    intf_desc_t *intf;
    intf_desc_t *intf_b;
    ep_desc_t *ep;
    acm_function * acm;
    /* setup device descriptor */
    pdesc->dev.idVendor = usb_vendor_id;
    pdesc->dev.idProduct    = usb_product_id;
    pdesc->dev.bNumConfigurations = 1;

    cfg = (config_desc_t*) (pdesc->cdb);

    cfg->bLength             = sizeof( config_desc_t );
    cfg->bDescriptorType     = USB_DESC_CONFIG;
    cfg->wTotalLength        = make_word_c( sizeof(config_desc_t) +
                           sizeof(intf_desc_t) * 2 +
                           sizeof(acm_function) +
                           sizeof(ep_desc_t) * 2);
    cfg->bNumInterfaces      = 2;
    cfg->bConfigurationValue = GS_ACM_CONFIG_ID;
    cfg->iConfiguration      = 0;
    cfg->bmAttributes        = 0xc0;//?
    cfg->MaxPower            = 0x01;

    intf = (intf_desc_t *) ( cfg + 1);
    intf->bLength            = sizeof( intf_desc_t );
    intf->bDescriptorType    = USB_DESC_INTERFACE;
    intf->bInterfaceNumber   = GS_CONTROL_INTERFACE_ID;
    intf->bAlternateSetting  = 0;
    intf->bNumEndpoints      = 1;
    intf->bInterfaceClass    = USB_CLASS_COMM;
    intf->bInterfaceSubClass = USB_CDC_SUBCLASS_ACM;
    intf->bInterfaceProtocol = USB_CDC_CTRL_PROTO_AT;
    intf->iInterface         = 0;

    acm = (acm_function *)(intf+1);
    acm->gs_call_mgmt_descriptor.bLength = 5;
    acm->gs_call_mgmt_descriptor.bDescriptorType = USB_DT_CS_INTERFACE;
    acm->gs_call_mgmt_descriptor.bDescriptorSubType = USB_CDC_CALL_MGMT;
    acm->gs_call_mgmt_descriptor.bmCapabilities = 0;
    acm->gs_call_mgmt_descriptor.bDataInterface = 0;

    acm->gs_acm_descriptor.bLength = 4;
    acm->gs_acm_descriptor.bDescriptorType = USB_DT_CS_INTERFACE;
    acm->gs_acm_descriptor.bDescriptorSubType = USB_CDC_ACM;
    acm->gs_acm_descriptor.bmCapabilities = 0x00;

    acm->gs_union_desc.bLength = 5;
    acm->gs_union_desc.bDescriptorType = USB_DT_CS_INTERFACE;
    acm->gs_union_desc.bDescriptorSubType = USB_CDC_UNION;
    acm->gs_union_desc.bMasterInterface0 = 0;
    acm->gs_union_desc.bSlaveInterface0 = 1;

    acm->gs_fullspeed_notify_desc.bLength = sizeof( ep_desc_t );
    acm->gs_fullspeed_notify_desc.bDescriptorType = USB_DESC_ENDPOINT;
    acm->gs_fullspeed_notify_desc.bEndpointAddress = 0x83;
    acm->gs_fullspeed_notify_desc.bmAttributes = USB_EP_INT;
    acm->gs_fullspeed_notify_desc.wMaxPacketSize = sizeof(SER_NOTIFY);
    acm->gs_fullspeed_notify_desc.bInterval = 0x01;

    intf_b = (intf_desc_t *) ( acm + 1);
    intf_b->bLength            = sizeof( intf_desc_t );
    intf_b->bDescriptorType    = USB_DESC_INTERFACE;
    intf_b->bInterfaceNumber   = GS_DATA_INTERFACE_ID;
    intf_b->bAlternateSetting  = 0;
    intf_b->bNumEndpoints      = 2;
    intf_b->bInterfaceClass    = USB_CLASS_CDC_DATA;
    intf_b->bInterfaceSubClass = 0;
    intf_b->bInterfaceProtocol = 0;
    intf_b->iInterface         = 0;

    ep = (ep_desc_t *) (intf_b + 1);
    ep[0].bLength             = sizeof( ep_desc_t );
    ep[0].bDescriptorType     = USB_DESC_ENDPOINT;
    ep[0].bEndpointAddress    = USB_EP_ADDRESS( 1, USB_IN );
    ep[0].bmAttributes        = USB_EP_BULK;
    if( usb_speed == USB_SPEED_HIGH )
        ep[0].wMaxPacketSize      = make_word( 512 );
    else
        ep[0].wMaxPacketSize      = make_word( 64 );
    ep[0].bInterval           = 0x01;

    ep[1].bLength             = sizeof( ep_desc_t );
    ep[1].bDescriptorType     = USB_DESC_ENDPOINT;
    ep[1].bEndpointAddress    = USB_EP_ADDRESS( 2, USB_OUT );
    ep[1].bmAttributes        = USB_EP_BULK;
    if( usb_speed == USB_SPEED_HIGH )
        ep[1].wMaxPacketSize      = make_word( 512 );
    else
        ep[1].wMaxPacketSize      = make_word( 64 );
    ep[1].bInterval  = 0x01;
    /* reset buffer */
    rx_done = 0;
    rx_size = 0;

    DBGOUT(printf("@after the init the descriptor & usb_connected = %d\n", usb_connected));

}

static inline int check_usb_connection(void)
{
    unsigned int cnt = 0;

    if (usb_connected)
        return 0;
    else
        u2d_start();

    /* waiting util connected */
    DBGOUT(printf("before while\n"));
    while (!usb_connected) {
        if ( ICPR2 & 0x4000 ) {
            u2d_int_hndlr(0x11, 0);
        }
    }
    DBGOUT(printf("After usb is connected!\n"));
    while ( cnt < 0x5000) {
        if ( ICPR2 & 0x4000 ) {
            DBGOUT(printf("GET udc interrpt\n"));
            u2d_int_hndlr(0x11, 0);
            cnt = 0;
        }
        cnt ++;
    }

    DBGOUT(printf("exit check_usb_connection:%d\n", usb_connected));
    return 0;
}


int usb_eth_init(u32 base)
{
    usb_connected = 0;

    /* enable u2d clock */
//  *(unsigned long *)0x4134000c |= 0x40;
    /* initialize PXA USB controller */
    pxa_usb_open("usb-eth");
    pxa_usb_start();

    return 0;
}

extern int req_pending;
void dump_buffer(char *buf, unsigned length);
extern int ep2_begin(int flag);

int usb_eth_rx(u8 *inbuf, int size)
{
    if(!usb_connected)
        check_usb_connection();
    DBGOUT(printf("\n########################################\n"));

    return ep2_recv(inbuf, size);
}

int usb_eth_tx(u8 *outbuf, int size)
{
    if(!usb_connected)
        check_usb_connection();

    DBGOUT(printf("\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"));
    return ep1_send(outbuf, size, usb_eth_tx_callback);
}
